
#include "BackupFS.h"
#include "BackupFSSql.h"
#include "../logger/BackupFSLogger.h"
namespace BackupFS
{
	/*
	Mount the BackupFS file system
	data_path: The folder will save all the file system files such as data file,db file and so on.the path must exist.
	mount_point: mount point,can be drive letter such as Z:\ or empty path c:\test\
	fs_size:the file system size.default is 0,no limtied
	*/
	bool BackupVFS::Mount(const std::wstring& data_path, const std::wstring mount_point,LONGLONG fs_size)
	{
		if (_Mounted)
		{
			Logger->Error("Already mounted the file system.");
			return false;
		}
		DokanInit();
		_DataPath = (data_path), _MountPoint = (mount_point);
		if (_MountPoint.size() > 0)
		{
			//remove the last "\" char
			RemoveTrailingSlash(_MountPoint);
		}
		bool has_db = false;
		/*
		Init the Core file instance
		*/
		if (!BackupFSCore::GetInstance()->Init(_DataPath, fs_size, has_db))
		{
			DokanShutdown();
			return false;
		}
		FsFileNodes = std::make_unique<BackupFSFileNodes>();
		FsFileNodes->SetFlushFrequency(_FlushFrequency);
		BackupFSCore::GetInstance()->SetReadOnly(_ReadOnly);
		/*
		Always add the root path
		*/
		FsFileNodes->AddRoot(!has_db);
		DOKAN_OPTIONS dokan_options;
		ZeroMemory(&dokan_options, sizeof(DOKAN_OPTIONS));
		dokan_options.Version = DOKAN_VERSION;
		dokan_options.Options = DOKAN_OPTION_ALT_STREAM |DOKAN_OPTION_CASE_SENSITIVE;
		dokan_options.MountPoint = _MountPoint.c_str();
		dokan_options.SingleThread = false;
		/*
		For supporting LockFile and UnlockFile APi.specify the flag DOKAN_OPTION_FILELOCK_USER_MODE
		*/
		dokan_options.Options |= DOKAN_OPTION_MOUNT_MANAGER| DOKAN_OPTION_FILELOCK_USER_MODE;
		
		dokan_options.GlobalContext = reinterpret_cast<ULONG64>(this);
		/*
		Create file system
		*/
		NTSTATUS status = DokanCreateFileSystem(&dokan_options, &BackupFSOperations, &_Instance);
		if (status != DOKAN_SUCCESS) 
		{
			DokanShutdown();
			Logger->Error("DOKAN_ERROR:",status);
			return false;
		}
		Logger->Info("Mount done.");
		_Mounted = true;
		return true;
	}
	LONGLONG BackupVFS::GetFSSize()
	{
		return BackupFSCore::GetInstance()->GetFsSize();
	}
	LONGLONG BackupVFS::GetFreeSize()
	{
		return BackupFSCore::GetInstance()->GetFreeSize();
	}
	/*
	Wait the file system closed
	*/
	void BackupVFS::Wait()
	{
		DokanWaitForFileSystemClosed(_Instance, INFINITE);
		// Release instance resources
		DokanCloseHandle(_Instance);
		Logger->Info("Completed the FS");
	}
	/*
	Set the cache number, the default is 10000000
	Means can take about 500Mb memory
	*/
	void BackupVFS::SetCacheNumber(ULONGLONG num)
	{
		if (FsFileNodes)
		{
			FsFileNodes->SetCacheNumber(num);
		}
	}
	/*
	Set flush frequency,default flush frequency is 3 seconds
	The minimum is 1 second
	*/
	void BackupVFS::SetFlushFrequency(int frequency)
	{
		_FlushFrequency = frequency;
		if (_FlushFrequency < 1)
		{
			_FlushFrequency = 1;
		}
		if (FsFileNodes)
		{
			FsFileNodes->SetFlushFrequency(_FlushFrequency);
		}
	}
	/*
	Current support normal,drill and incremental modes

	*/
	void BackupVFS::SetMode(FILE_SYSTEM_MODE mode)
	{
		_Mode = mode;
		BackupFSCore::GetInstance()->SetMode(mode);
	}
	/*
	Readonly status,it's not a real mode,just is a status
	*/
	void BackupVFS::SetReadOnly(bool read_only)
	{
		_ReadOnly = read_only;
		BackupFSCore::GetInstance()->SetReadOnly(_ReadOnly);
	}
	void BackupVFS::SetFsSize(LONGLONG size)
	{
		BackupFSCore::GetInstance()->SetFsSize(size);
	}
	/*
	Create a new file,if the file name existed,return false
	If open existed file,use OpenFile
	*/
	bool BackupVFS::CreateFile(const std::wstring& file_name, LONGLONG file_size, DWORD attributes)
	{
		//replace the drive letter with "\"
		if (file_name.length() <= _MountPoint.length())
		{
			return false;
		}
		//Mount point is like z:\ or "c:\temp\"
		std::wstring file_name_str = file_name.substr(_MountPoint.length() );
		
		auto file = FsFileNodes->Find(file_name_str);
		if (file)
		{
			return false;
		}
		/*
		Create a file node directly
		*/
		const auto real_fileName = BackupFSUtils::GetFileName(file_name_str);
		auto node = std::make_shared<BackupFSFileNode>(file_name_str, false, attributes, nullptr);
		if (STATUS_SUCCESS != FsFileNodes->Add(node, std::pair<std::wstring, std::wstring>(real_fileName, std::wstring())))
		{
			return false;
		}
		node->SetEndOfFile(file_size);
		return true;
	}
	bool BackupVFS::ReadFile(const std::wstring& file_name, LPVOID buffer, DWORD bufferlength, LPDWORD readlength, LONGLONG offset)
	{
		if (file_name.length() <= _MountPoint.length())
		{
			return false;
		}
		//Mount point is like z:\ or "c:\temp\"
		std::wstring file_name_str = file_name.substr(_MountPoint.length());
		auto file = FsFileNodes->Find(file_name_str);
		if (!file)
		{
			return false;
		}
		*readlength = file->Read(buffer, bufferlength, offset);
		return true;
	}
	bool BackupVFS::WriteFile(const std::wstring& file_name, LPCVOID buffer, DWORD number_of_bytes_to_write, LPDWORD number_of_bytes_written, LONGLONG offset)
	{
		if (file_name.length() <= _MountPoint.length())
		{
			return false;
		}
		//Mount point is like z:\ or "c:\temp\"
		std::wstring file_name_str = file_name.substr(_MountPoint.length() );
		auto file = FsFileNodes->Find(file_name_str);
		if (!file)
		{
			return false;
		}
		*number_of_bytes_written=file->Write(buffer, number_of_bytes_to_write, offset);
		return true;
	}
	void BackupVFS::CloseFile(const std::wstring& file_name)
	{
		if (file_name.length() <= _MountPoint.length())
		{
			return ;
		}
		std::wstring file_name_str = file_name.substr(2);
		auto file = FsFileNodes->Find(file_name_str);
		if (!file)
		{
			return ;
		}
		file->Close(0);
	}
	void BackupVFS::Umount() 
	{
		DokanRemoveMountPoint(_MountPoint.c_str()); 
		FsFileNodes->Stop();
		DokanShutdown();
		_Mounted = false;
	}
	std::wstring BackupVFS::GetMountPoint()
	{
		return _MountPoint;
	}
	void BackupVFS::SetMountPoint(const std::wstring& mount_point)
	{
		_MountPoint = mount_point;
	}
} // namespace BackupFS
